<!DOCTYPE html>
<html lang='zh-CN'>

<head>
  <meta name="generator" content="Hexo 5.4.2">
  <meta name="hexo-theme" content="https://github.com/xaoxuu/hexo-theme-stellar/tree/1.18.5">
  <meta charset="utf-8">
  

  <meta http-equiv='x-dns-prefetch-control' content='on' />
  <link rel='dns-prefetch' href='https://gcore.jsdelivr.net'>
  <link rel="preconnect" href="https://gcore.jsdelivr.net" crossorigin>
  <link rel='dns-prefetch' href='//unpkg.com'>

  <meta name="renderer" content="webkit">
  <meta name="force-rendering" content="webkit">
  <meta http-equiv="X-UA-Compatible" content="IE=Edge,chrome=1">
  <meta name="HandheldFriendly" content="True" >
  <meta name="apple-mobile-web-app-capable" content="yes">
  <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
  <meta name="theme-color" content="#f8f8f8">
  
  <title>如何使用web3.py与以太坊网络交互 - Matrix.Ye 个人博客</title>

  
    <meta name="description" content="web3.py是基于python的以太坊库，内部封装了对于etereum的rpc请求，这篇文章介绍了使用web3.py v6与以太坊或以太坊系列的区块链网络进行交互的常用方式。 安装web3.py1pip install web3 加载以太坊节点：12from web3 import Web3w3 &#x3D; Web3(Web3.HTTPProvider(&amp;#x27;https:&#x2F;&#x2F;&lt;your-pr">
<meta property="og:type" content="article">
<meta property="og:title" content="如何使用web3.py与以太坊网络交互">
<meta property="og:url" content="https://gitee.com/initmatrix/initmatrix/2023/04/20/%E5%A6%82%E4%BD%95%E4%BD%BF%E7%94%A8web3-py%E4%B8%8E%E4%BB%A5%E5%A4%AA%E5%9D%8A%E7%BD%91%E7%BB%9C%E4%BA%A4%E4%BA%92/index.html">
<meta property="og:site_name" content="Matrix.Ye 个人博客">
<meta property="og:description" content="web3.py是基于python的以太坊库，内部封装了对于etereum的rpc请求，这篇文章介绍了使用web3.py v6与以太坊或以太坊系列的区块链网络进行交互的常用方式。 安装web3.py1pip install web3 加载以太坊节点：12from web3 import Web3w3 &#x3D; Web3(Web3.HTTPProvider(&amp;#x27;https:&#x2F;&#x2F;&lt;your-pr">
<meta property="og:locale" content="zh_CN">
<meta property="article:published_time" content="2023-04-20T14:07:39.000Z">
<meta property="article:modified_time" content="2023-04-20T14:08:01.112Z">
<meta property="article:author" content="yepeng">
<meta name="twitter:card" content="summary">
  
  
  
  

  <!-- feed -->
  

  
    
<link rel="stylesheet" href="/initmatrix/initmatrix/css/main.css">

  

  

  

  


  
</head>

<body>
  




  <div class='l_body' id='start'>
    <aside class='l_left' layout='post'>
    

  

<header class="header"><div class="logo-wrap"><a class="title" href="/initmatrix/initmatrix/"><div class="main" ff="title">Matrix.Ye 个人博客</div><div class="sub cap">个人博客</div></a></div>

<nav class="menu dis-select"></nav>
</header>


<div class="widgets">
<widget class="widget-wrapper search"><div class="widget-body"><div class="search-wrapper" id="search"><form class="search-form"><input type="text" class="search-input" id="search-input" data-filter="/blog/" placeholder="文章搜索"><svg t="1670596976048" class="icon search-icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2676" width="200" height="200"><path d="M938.2 832.6L723.8 618.1c-2.5-2.5-5.3-4.4-7.9-6.4 36.2-55.6 57.3-121.8 57.3-193.1C773.3 222.8 614.6 64 418.7 64S64 222.8 64 418.6c0 195.9 158.8 354.6 354.6 354.6 71.3 0 137.5-21.2 193.2-57.4 2 2.7 3.9 5.4 6.3 7.8L832.5 938c14.6 14.6 33.7 21.9 52.8 21.9 19.1 0 38.2-7.3 52.8-21.8 29.2-29.1 29.2-76.4 0.1-105.5M418.7 661.3C284.9 661.3 176 552.4 176 418.6 176 284.9 284.9 176 418.7 176c133.8 0 242.6 108.9 242.6 242.7 0 133.7-108.9 242.6-242.6 242.6" p-id="2677"></path></svg></form><div id="search-result"></div><div class="search-no-result">没有找到内容！</div></div></div></widget>


<widget class="widget-wrapper toc single" id="data-toc"><div class="widget-header cap dis-select"><span class="name">如何使用web3.py与以太坊网络交互</span></div><div class="widget-body fs14"><div class="doc-tree active"><ol class="toc"><li class="toc-item toc-level-3"><a class="toc-link" href="#%E5%AE%89%E8%A3%85web3-py"><span class="toc-text">安装web3.py</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E5%8A%A0%E8%BD%BD%E4%BB%A5%E5%A4%AA%E5%9D%8A%E8%8A%82%E7%82%B9%EF%BC%9A"><span class="toc-text">加载以太坊节点：</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E8%8E%B7%E5%8F%96%E5%8C%BA%E5%9D%97%E8%AF%A6%E6%83%85"><span class="toc-text">获取区块详情</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E5%B8%B8%E7%94%A8%E5%87%BD%E6%95%B0%E5%92%8C%E5%B7%A5%E5%85%B7%E6%96%B9%E6%B3%95"><span class="toc-text">常用函数和工具方法</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E4%BB%A5%E5%A4%AA%E5%9D%8A%E5%8E%9F%E7%94%9F%E4%BA%A4%E6%98%93"><span class="toc-text">以太坊原生交易</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E8%B0%83%E7%94%A8%E5%90%88%E7%BA%A6%E6%96%B9%E6%B3%95"><span class="toc-text">调用合约方法</span></a><ol class="toc-child"><li class="toc-item toc-level-4"><a class="toc-link" href="#%E8%B0%83%E7%94%A8%E5%90%88%E7%BA%A6%E7%9A%84View%E6%96%B9%E6%B3%95"><span class="toc-text">调用合约的View方法:</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#%E8%B0%83%E7%94%A8%E5%90%88%E7%BA%A6%E9%9D%9EView%E6%96%B9%E6%B3%95"><span class="toc-text">调用合约非View方法</span></a></li></ol></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E8%BF%87%E6%BB%A4%E5%90%88%E7%BA%A6%E6%97%A5%E5%BF%97"><span class="toc-text">过滤合约日志</span></a></li></ol></div></div></widget>




</div>


    </aside>
    <div class='l_main'>
      

      



<div class="bread-nav fs12"><div id="breadcrumb"><a class="cap breadcrumb" href="/initmatrix/initmatrix/">主页</a><span class="sep"></span><a class="cap breadcrumb" href="/initmatrix/initmatrix/">文章</a></div><div id="post-meta">发布于&nbsp;<time datetime="2023-04-20T14:07:39.000Z">2023-04-20</time></div></div>

<article class='md-text content post'>
<h1 class="article-title"><span>如何使用web3.py与以太坊网络交互</span></h1>
<p><code>web3.py</code>是基于python的以太坊库，内部封装了对于etereum的rpc请求，这篇文章介绍了使用web3.py v6与以太坊或以太坊系列的区块链网络进行交互的常用方式。</p>
<h3 id="安装web3-py"><a href="#安装web3-py" class="headerlink" title="安装web3.py"></a>安装web3.py</h3><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">pip install web3</span><br></pre></td></tr></table></figure>
<h3 id="加载以太坊节点："><a href="#加载以太坊节点：" class="headerlink" title="加载以太坊节点："></a>加载以太坊节点：</h3><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> web3 <span class="keyword">import</span> Web3</span><br><span class="line">w3 = Web3(Web3.HTTPProvider(<span class="string">&#x27;https://&lt;your-provider-url&gt;&#x27;</span>))</span><br></pre></td></tr></table></figure>
<p>如果是本地节点，例如ganache,地址为<code>http://localhost:8545</code>如果是线上节点，一般使用一些节点服务商提供的节点地址，例如<code>Infura</code>、<code>quicknode</code>、<code>alchemy</code>等。小狐狸metamast只是个钱包入口，它不提供节点api，它本身使用的也是infur提供的节点。智能合约开发框架<code>truffle</code>、<code>truffle</code>都提供了本地用于测试的私链，同样可以通过节点连接。</p>
<h3 id="获取区块详情"><a href="#获取区块详情" class="headerlink" title="获取区块详情"></a>获取区块详情</h3><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">w3.eth.get_block(<span class="string">&#x27;latest&#x27;</span>)</span><br></pre></td></tr></table></figure>
<p>这个方法可以使用’latset’获取最新块，也可以指定高度、区块哈希获取指定高度，返回一堆区块头数据</p>
<figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span>&#x27;difficulty&#x27;<span class="punctuation">:</span> <span class="number">1</span><span class="punctuation">,</span></span><br><span class="line"> &#x27;gasLimit&#x27;<span class="punctuation">:</span> <span class="number">6283185</span><span class="punctuation">,</span></span><br><span class="line"> &#x27;gasUsed&#x27;<span class="punctuation">:</span> <span class="number">0</span><span class="punctuation">,</span></span><br><span class="line"> &#x27;hash&#x27;<span class="punctuation">:</span> HexBytes(&#x27;<span class="number">0x0</span>&#x27;)<span class="punctuation">,</span></span><br><span class="line"> &#x27;logsBloom&#x27;<span class="punctuation">:</span> HexBytes(&#x27;<span class="number">0x000</span>&#x27;)<span class="punctuation">,</span></span><br><span class="line"> &#x27;miner&#x27;<span class="punctuation">:</span> &#x27;<span class="number">0x0</span>&#x27;<span class="punctuation">,</span></span><br><span class="line"> &#x27;mixHash&#x27;<span class="punctuation">:</span> HexBytes(&#x27;<span class="number">0x0</span>&#x27;)<span class="punctuation">,</span></span><br><span class="line"> &#x27;nonce&#x27;<span class="punctuation">:</span> HexBytes(&#x27;<span class="number">0x0</span>&#x27;)<span class="punctuation">,</span></span><br><span class="line"> &#x27;number&#x27;<span class="punctuation">:</span> <span class="number">0</span><span class="punctuation">,</span></span><br><span class="line"> &#x27;parentHash&#x27;<span class="punctuation">:</span> HexBytes(&#x27;<span class="number">0x000</span>&#x27;)<span class="punctuation">,</span></span><br><span class="line"> &#x27;proofOfAuthorityData&#x27;<span class="punctuation">:</span> HexBytes(&#x27;<span class="number">0x000</span>&#x27;)<span class="punctuation">,</span></span><br><span class="line"> &#x27;receiptsRoot&#x27;<span class="punctuation">:</span> HexBytes(&#x27;<span class="number">0x0</span>&#x27;)<span class="punctuation">,</span></span><br><span class="line"> &#x27;sha3Uncles&#x27;<span class="punctuation">:</span> HexBytes(&#x27;<span class="number">0x0</span>&#x27;)<span class="punctuation">,</span></span><br><span class="line"> &#x27;size&#x27;<span class="punctuation">:</span> <span class="number">622</span><span class="punctuation">,</span></span><br><span class="line"> &#x27;stateRoot&#x27;<span class="punctuation">:</span> HexBytes(&#x27;<span class="number">0x0</span>&#x27;)<span class="punctuation">,</span></span><br><span class="line"> &#x27;timestamp&#x27;<span class="punctuation">:</span> <span class="number">0</span><span class="punctuation">,</span></span><br><span class="line"> &#x27;totalDifficulty&#x27;<span class="punctuation">:</span> <span class="number">1</span><span class="punctuation">,</span></span><br><span class="line"> &#x27;transactions&#x27;<span class="punctuation">:</span> <span class="punctuation">[</span><span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line"> &#x27;transactionsRoot&#x27;<span class="punctuation">:</span> HexBytes(&#x27;<span class="number">0x0</span>&#x27;)<span class="punctuation">,</span></span><br><span class="line"> &#x27;uncles&#x27;<span class="punctuation">:</span> <span class="punctuation">[</span><span class="punctuation">]</span><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure>

<h3 id="常用函数和工具方法"><a href="#常用函数和工具方法" class="headerlink" title="常用函数和工具方法"></a>常用函数和工具方法</h3><ol>
<li>获取当前gasprice <code>w3.eth.gase_price</code>，经常需要使用</li>
<li>获取当前区块高度 <code>w3.eth.block_number</code></li>
<li>获取当前链ID <code>w3.eth.chain_id</code></li>
<li>获取某账户的余额 <code>w3.eth.get_balance(account=&#39;0x0&#39;,block_identifier=111111)</code> 注意是eth余额，不是token的.注意第二个参数<code>block_identifier</code>可以指定区块高度，或者区块哈希</li>
<li><code>Web3.toChecksumAddress(address)</code>：将地址（字符串类型）转换为校验和形式，以提高地址的安全性。</li>
<li><code>Web3.keccak(text)</code>：计算一个字符串的 Keccak-256 哈希值。在 Solidity 中，也可以使用 <code>keccak256</code> 函数计算一个字符串的哈希值。</li>
<li><code>Web3.is_address(address)</code>：判断一个地址是否为合法的以太坊地址。</li>
<li><code>Web3.to_wei(1,&#39;ether&#39;)</code> 数值转化,将ether转为wei</li>
<li><code>Web3.from_wei(1000000000000000000, &#39;ether&#39;)</code>:将单位wei转化为其他单位</li>
</ol>
<h3 id="以太坊原生交易"><a href="#以太坊原生交易" class="headerlink" title="以太坊原生交易"></a>以太坊原生交易</h3><p>受益于以太坊的常规账户模型，转账的逻辑要比BTC的UTXO模型要简洁清晰得多，在以太坊系统中从A向B转账，就是把A账户中的余额扣除，把B账户中的余额增加。以下是转账的基本流程代码：</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> eth_account.account <span class="keyword">import</span> Account, LocalAccount, SignedTransaction  </span><br><span class="line"><span class="keyword">from</span> web3 <span class="keyword">import</span> Web3, HTTPProvider  </span><br><span class="line">  </span><br><span class="line"><span class="comment"># 以太坊原生交易s  </span></span><br><span class="line">sender_addr = <span class="string">&quot;0x1111111111111111111111111111...&quot;</span>  </span><br><span class="line">sender_private_key = <span class="string">&quot;0xffffffffffffffffffffffff...&quot;</span>  </span><br><span class="line">to_address = <span class="string">&quot;0xB1476dFdAD625D787A006Ed362EBa5872a88Ae1A&quot;</span>  </span><br><span class="line">  </span><br><span class="line">w3 = Web3(HTTPProvider(<span class="string">&quot;http://127.0.0.1:8545&quot;</span>))  </span><br><span class="line"><span class="comment">## 1、构建交易数据  </span></span><br><span class="line">tx = &#123;  </span><br><span class="line">    <span class="string">&#x27;from&#x27;</span>: sender_addr,  <span class="comment"># 如果传入的地址不是发送者的地址，那么会出现typeerror的错误  </span></span><br><span class="line">    <span class="string">&#x27;to&#x27;</span>: to_address,  </span><br><span class="line">    <span class="string">&#x27;value&#x27;</span>: w3.to_wei(<span class="number">1</span>, <span class="string">&#x27;ether&#x27;</span>),  <span class="comment"># 这里的单位是wei,10**18 wei = 1 ether</span></span><br><span class="line">    <span class="string">&#x27;gas&#x27;</span>: <span class="number">21000</span>,  </span><br><span class="line">    <span class="string">&#x27;gasPrice&#x27;</span>: w3.eth.gas_price,  </span><br><span class="line">    <span class="string">&#x27;nonce&#x27;</span>: w3.eth.get_transaction_count(sender_addr)  </span><br><span class="line">&#125;  </span><br><span class="line"><span class="comment">## 2、签名交易  </span></span><br><span class="line">account: LocalAccount = Account.from_key(sender_private_key)  </span><br><span class="line">sign_tx: SignedTransaction = account.sign_transaction(tx)  </span><br><span class="line"><span class="comment">## 3、发送交易  </span></span><br><span class="line">tx_hash = w3.eth.send_raw_transaction(sign_tx.rawTransaction)  </span><br><span class="line"><span class="comment">## 4、阻塞等待交易结果  </span></span><br><span class="line">result = w3.eth.wait_for_transaction_receipt(tx_hash, timeout=<span class="number">60</span>)  </span><br><span class="line"><span class="built_in">print</span>(result)</span><br></pre></td></tr></table></figure>
<p>注意：</p>
<ol>
<li>交易数据中<code>from</code>这个字段可以不传的，但如果使用，必须保证地址与使用的私钥匹配，否则无法进行签名</li>
<li>交易能否执行成功与gas有关，gas过低会造成以太坊虚拟机无法执行事务，高了会返回多余gas，原生交易固定为21000，其他交易可以预估<code>web3.eth.estimate_gas(tx)</code>，但一般直接上限写死即可，比如100000</li>
<li>能否上链被矿工打包与gas无关，与<code>gasPrice</code>相关，gas价格太低了矿工嫌弃你，太高了你嫌弃矿工，web3.py提供了预估当前最佳gasprice的方法<code>w3.eth.gase_price</code>，你和矿工都满意</li>
<li><code>value</code>这个字段是交易的值，单位是Wei，对于原生交易，这个值表示从A转给B的钱，对于合约交易，这个值一般为0。</li>
<li><code>nonce</code>这个值是记录交易发起者交易序列的，每次转账都比上一次的nonce加1，使用w<code>b3.eth.get_transaction_count(sender)</code>获取sender的nonce值(注意，这个值已经进行了加1)</li>
<li>web3.py的签名过程不同版本还不一样，这里使用的是<code>v6.1.0</code></li>
<li>这里使用的是eth_account提供的Account进行签名，对于原生交易和合约交易效果一样，但可能对于一些新EIP的交易类型不支持</li>
<li>签名的两个参数，一个是需要签名的交易数据，一个是私钥，注意私钥的保密</li>
<li>签名完成后会生成Raw交易数据，结构如下，实际上最后用到的只有<code>rawTransaction</code>这个字段<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">&#123;</span><br><span class="line"> <span class="string">&#x27;hash&#x27;</span>: HexBytes(<span class="string">&#x27;0x126431f2a7fda003aada7c2ce52b0ce3cbdbb1896230d3333b9eea24f42d15b0&#x27;</span>), </span><br><span class="line"> <span class="string">&#x27;r&#x27;</span>: <span class="number">110093478023675319011132687961420618950720745285952062287904334878381994888509</span>,</span><br><span class="line"> <span class="string">&#x27;rawTransaction&#x27;</span>:HexBytes(<span class="string">&#x27;0x........&#x27;</span>), </span><br><span class="line"> <span class="string">&#x27;s&#x27;</span>: <span class="number">33674551144139401179914073499472892825822542092106065756005379322302694600392</span>,  </span><br><span class="line"> <span class="string">&#x27;v&#x27;</span>: <span class="number">0</span>&#125;</span><br></pre></td></tr></table></figure>
注意，发送过程是异步的，以太坊从交易发送到节点确认需要一定的时间，主网一般10～15秒，<code>goerli</code>这类测试网要快一点，如<code>arbitrum</code>这类L2的网络最快基本上1～3秒。<br>发送交易后可以等待交易确认再执行后续操作，使用方法：</li>
</ol>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">resp = w3.eth.wait_for_transaction_receipt(tx_hash)</span><br></pre></td></tr></table></figure>
<p>这个方法其实是一个死循环在请求节点获取交易哈希状态结果，默认超时120秒</p>
<p>可以将上述流程封装成函数，方便调用</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> eth_account.account <span class="keyword">import</span> Account, LocalAccount, SignedTransaction  </span><br><span class="line"><span class="keyword">from</span> hexbytes <span class="keyword">import</span> HexBytes  </span><br><span class="line"><span class="keyword">from</span> web3 <span class="keyword">import</span> Web3, HTTPProvider  </span><br><span class="line">  </span><br><span class="line">  </span><br><span class="line"><span class="keyword">def</span> <span class="title function_">transfer</span>(<span class="params">w3: Web3, account: LocalAccount, to: <span class="built_in">str</span>, value: <span class="built_in">int</span>, gas=<span class="number">21000</span></span>) -&gt; HexBytes:  </span><br><span class="line">     <span class="string">&quot;&quot;&quot;</span></span><br><span class="line"><span class="string">    交易、普通转账</span></span><br><span class="line"><span class="string">    :param w3: 以太坊客户端Web3实例</span></span><br><span class="line"><span class="string">    :param account: 以太坊账户，由eth-account提供</span></span><br><span class="line"><span class="string">    :param to: 接收地址</span></span><br><span class="line"><span class="string">    :param value: 转账金额,单位时wei,1 ether=10**18 wei</span></span><br><span class="line"><span class="string">    :param gas: gas消耗，默认21000，不能比这个低</span></span><br><span class="line"><span class="string">    :return: 交易哈希 字节数组</span></span><br><span class="line"><span class="string">    &quot;&quot;&quot;</span></span><br><span class="line">    <span class="comment">## 1、构建交易数据  </span></span><br><span class="line">    tx = &#123;  </span><br><span class="line">        <span class="string">&#x27;from&#x27;</span>: account.address,  </span><br><span class="line">        <span class="string">&#x27;to&#x27;</span>: to,  </span><br><span class="line">        <span class="string">&#x27;value&#x27;</span>: value,  </span><br><span class="line">        <span class="string">&#x27;gas&#x27;</span>: gas,  </span><br><span class="line">        <span class="string">&#x27;gasPrice&#x27;</span>: w3.eth.gas_price,  </span><br><span class="line">        <span class="string">&#x27;nonce&#x27;</span>: w3.eth.get_transaction_count(account.address)  </span><br><span class="line">    &#125;  </span><br><span class="line">    <span class="comment">## 2、签名  </span></span><br><span class="line">    sign_tx: SignedTransaction = account.sign_transaction(tx)  </span><br><span class="line">    <span class="comment">## 3、发送  </span></span><br><span class="line">    <span class="keyword">return</span> w3.eth.send_raw_transaction(sign_tx.rawTransaction)</span><br><span class="line">    </span><br><span class="line"><span class="keyword">if</span> __name__ == <span class="string">&#x27;__main__&#x27;</span>:  </span><br><span class="line">    w3 = Web3(HTTPProvider(endpoint_uri=<span class="string">&#x27;http://127.0.0.1:8545&#x27;</span>))  </span><br><span class="line">    account = Account.from_key(<span class="string">&quot;&lt;你的私钥&gt;&quot;</span>)  </span><br><span class="line">    tx_hash = transfer(w3, account, <span class="string">&#x27;&lt;接收地址&gt;&#x27;</span>, <span class="number">1</span>*<span class="number">10</span>**<span class="number">17</span>)  </span><br><span class="line">    <span class="built_in">print</span>(<span class="string">f&quot;Success Transfer Hash:<span class="subst">&#123;tx_hash.<span class="built_in">hex</span>()&#125;</span>&quot;</span>)  </span><br><span class="line">    result = w3.eth.wait_for_transaction_receipt(tx_hash)  </span><br><span class="line">    <span class="built_in">print</span>(<span class="string">f&quot;Transfer Receipt:<span class="subst">&#123;result&#125;</span>&quot;</span>)</span><br></pre></td></tr></table></figure>

<h3 id="调用合约方法"><a href="#调用合约方法" class="headerlink" title="调用合约方法"></a>调用合约方法</h3><p>智能合约一旦部署就不允许再修改，后续对于合约的使用绝大部分集中在<strong>合约方法调用</strong>和<strong>过滤合约日志</strong></p>
<ol>
<li>智能合约的方法从外部调用的角度可以分为两种，一种是view方法，一种是非view方法。</li>
</ol>
<ul>
<li>view方法只是从区块链上查询数据，不会进行数据更改，不需要调用者进行签名操作</li>
<li>非view方法需要修改区块链上数据，需要调用者进行签名操作</li>
</ul>
<ol start="2">
<li>调用合约需要生成一个合约实例，这取决于两个东西，一是合约<code>address</code>，二是合约<code>ABI</code></li>
</ol>
<p>在本文中，需要部署一个简单的合约到区块链上:</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// SPDX-License-Identifier: GPL-3.0</span></span><br><span class="line">pragma solidity ^<span class="number">0.8</span><span class="number">.9</span>;</span><br><span class="line"></span><br><span class="line">contract <span class="title class_">Demo</span> &#123;</span><br><span class="line">    <span class="title function_">mapping</span>(<span class="function"><span class="params">address</span> =&gt;</span> uint) private _ages;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">function</span> <span class="title function_">setAge</span>(<span class="params">uint age</span>) public &#123;</span><br><span class="line">        _ages[msg.<span class="property">sender</span>] = age;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">function</span> <span class="title function_">getAge</span>(<span class="params">address user</span>) public view returns (uint) &#123;</span><br><span class="line">        <span class="keyword">return</span> _ages[user];</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure>

<p>在上面的合约中，提供了一个状态变量<code>_ages</code>来记录地址和年龄的映射关系，并提供了两个方法，<code>setAge(uint age)</code>设置调用者的年龄，<code>getAge(address user)</code>获取指定地址的年龄。</p>
<h4 id="调用合约的View方法"><a href="#调用合约的View方法" class="headerlink" title="调用合约的View方法:"></a>调用合约的View方法:</h4><p>对于View方法，调用方法分为几步：</p>
<ol>
<li>加载Web3实例</li>
<li>加载合约实例</li>
<li>获取合约方法对象</li>
<li>使用<code>call()</code>方法调用</li>
</ol>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> web3 <span class="keyword">import</span> Web3, HTTPProvider</span><br><span class="line"></span><br><span class="line"><span class="comment"># 连接以太坊节点</span></span><br><span class="line">w3 = Web3(HTTPProvider(endpoint_uri=<span class="string">&#x27;http://127.0.0.1:8545&#x27;</span>))</span><br><span class="line"></span><br><span class="line"><span class="comment"># 获取合约实例</span></span><br><span class="line">contract_address = <span class="string">&#x27;&lt;合约地址&gt;&#x27;</span></span><br><span class="line">contract_abi = [] <span class="comment"># 合约abi,这是一个list结构</span></span><br><span class="line">contract_instance = w3.eth.contract(address=contract_address, abi=contract_abi)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 获取View函数对象并调用</span></span><br><span class="line">func = <span class="built_in">getattr</span>(contract_instance.functions, <span class="string">&#x27;getAge&#x27;</span>)(user=<span class="string">&#x27;0xC3845C061D0e0929744B01CbB6c23c31402B3E3a&#x27;</span>)</span><br><span class="line">result=func.call()</span><br><span class="line"><span class="built_in">print</span>(<span class="string">f&quot;result=<span class="subst">&#123;result&#125;</span>)</span></span><br><span class="line"><span class="string"></span></span><br></pre></td></tr></table></figure>
<h4 id="调用合约非View方法"><a href="#调用合约非View方法" class="headerlink" title="调用合约非View方法"></a>调用合约非View方法</h4><p>对于非View方法，则分为下面几步：</p>
<ol>
<li>加载Web3实例</li>
<li>加载合约实例</li>
<li>获取合约方法对象</li>
<li>将方法对象绑定到交易数据中的<code>data</code>字段中</li>
<li>对交易数据进行签名</li>
<li>发送签名后的tx</li>
</ol>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> eth_account.account <span class="keyword">import</span> Account, SignedTransaction</span><br><span class="line"><span class="keyword">from</span> web3 <span class="keyword">import</span> Web3, HTTPProvider</span><br><span class="line"></span><br><span class="line"><span class="comment"># 调用者地址</span></span><br><span class="line">sender_address = <span class="string">&#x27;&lt;调用者地址&gt;&#x27;</span></span><br><span class="line">sender_privatekey = <span class="string">&#x27;&lt;调用者私钥&gt;&#x27;</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 连接以太坊节点</span></span><br><span class="line">w3 = Web3(HTTPProvider(endpoint_uri=<span class="string">&#x27;http://127.0.0.1:8545&#x27;</span>))</span><br><span class="line"></span><br><span class="line"><span class="comment"># 获取合约实例</span></span><br><span class="line">contract_address = <span class="string">&#x27;0x6EC21D47FCF6F06eeE9b36Ee85Ae25417b44697a&#x27;</span></span><br><span class="line">contract_abi = [] <span class="comment"># 合约ABI，这是一个List结构</span></span><br><span class="line">contract_instance = w3.eth.contract(address=contract_address, abi=contract_abi)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 获取调用函数对象</span></span><br><span class="line">func = <span class="built_in">getattr</span>(contract_instance.functions, <span class="string">&#x27;setAge&#x27;</span>)(age=<span class="number">18</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 构造交易数据</span></span><br><span class="line">tx = func.build_transaction(&#123;</span><br><span class="line">    <span class="string">&#x27;nonce&#x27;</span>: w3.eth.get_transaction_count(sender_address),</span><br><span class="line">    <span class="string">&#x27;value&#x27;</span>: <span class="number">0</span>,</span><br><span class="line">    <span class="string">&#x27;gas&#x27;</span>: <span class="number">100000</span>,</span><br><span class="line">    <span class="string">&#x27;gasPrice&#x27;</span>: w3.eth.gas_price&#125;)</span><br><span class="line"></span><br><span class="line">sign_tx: SignedTransaction = Account.sign_transaction(tx, sender_privatekey)</span><br><span class="line">tx_hash = w3.eth.send_raw_transaction(sign_tx.rawTransaction)</span><br><span class="line"><span class="built_in">print</span>(tx_hash.<span class="built_in">hex</span>())</span><br><span class="line"></span><br></pre></td></tr></table></figure>

<p>同样，可以将上述过程封装成函数，方便调用：</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="keyword">from</span> eth_account.account <span class="keyword">import</span> Account, LocalAccount, SignedTransaction</span><br><span class="line"><span class="keyword">from</span> web3 <span class="keyword">import</span> Web3, HTTPProvider</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">call_contract_sign_func</span>(<span class="params">w3: Web3, account: LocalAccount, address: <span class="built_in">str</span>, abi: <span class="built_in">list</span>, func_name: <span class="built_in">str</span>,</span></span><br><span class="line"><span class="params">                            gas=<span class="number">200000</span>, value=<span class="number">0</span>, **kwargs</span>):</span><br><span class="line">    <span class="string">&quot;&quot;&quot;</span></span><br><span class="line"><span class="string">    调用合约非View方法</span></span><br><span class="line"><span class="string">    :param w3: Web3 实例</span></span><br><span class="line"><span class="string">    :param account: 账户实例</span></span><br><span class="line"><span class="string">    :param address: 合约地址</span></span><br><span class="line"><span class="string">    :param abi: 合约ABI</span></span><br><span class="line"><span class="string">    :param func_name: 合约方法名</span></span><br><span class="line"><span class="string">    :param gas: 燃气,默认200000</span></span><br><span class="line"><span class="string">    :param value: 附带转账金额，默认0</span></span><br><span class="line"><span class="string">    :param kwargs: 方法参数</span></span><br><span class="line"><span class="string">    :return: 交易哈希字节数组</span></span><br><span class="line"><span class="string">    &quot;&quot;&quot;</span></span><br><span class="line">    contract_instance = w3.eth.contract(address=address, abi=abi)</span><br><span class="line">    func = <span class="built_in">getattr</span>(contract_instance.functions, func_name)(**kwargs)</span><br><span class="line">    <span class="comment"># 构造交易数据</span></span><br><span class="line">    tx = func.build_transaction(&#123;</span><br><span class="line">        <span class="string">&#x27;nonce&#x27;</span>: w3.eth.get_transaction_count(account.address),</span><br><span class="line">        <span class="string">&#x27;value&#x27;</span>: value,</span><br><span class="line">        <span class="string">&#x27;gas&#x27;</span>: gas,</span><br><span class="line">        <span class="string">&#x27;gasPrice&#x27;</span>: w3.eth.gas_price&#125;)</span><br><span class="line">    <span class="comment"># 签名</span></span><br><span class="line">    sign_tx: SignedTransaction = account.sign_transaction(tx)</span><br><span class="line">    <span class="comment"># 发送</span></span><br><span class="line">    <span class="keyword">return</span> w3.eth.send_raw_transaction(sign_tx.rawTransaction)</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">call_contract_view_func</span>(<span class="params">w3, address, abi, func_name, **kwargs</span>):</span><br><span class="line">    <span class="string">&quot;&quot;&quot;</span></span><br><span class="line"><span class="string">    调用合约View方法</span></span><br><span class="line"><span class="string">    :param w3:Web3实例</span></span><br><span class="line"><span class="string">    :param address:合约地址</span></span><br><span class="line"><span class="string">    :param abi:合约ABI</span></span><br><span class="line"><span class="string">    :param func_name:合约方法名称</span></span><br><span class="line"><span class="string">    :param kwargs:合约方法参数</span></span><br><span class="line"><span class="string">    :return:函数执行结果</span></span><br><span class="line"><span class="string">    &quot;&quot;&quot;</span></span><br><span class="line">    contract_instance = w3.eth.contract(address=address, abi=abi)</span><br><span class="line">    func = <span class="built_in">getattr</span>(contract_instance.functions, func_name)(**kwargs)</span><br><span class="line">    <span class="keyword">return</span> func.call()</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> __name__ == <span class="string">&#x27;__main__&#x27;</span>:</span><br><span class="line">    w = Web3(HTTPProvider(endpoint_uri=<span class="string">&#x27;http://127.0.0.1:8545&#x27;</span>))</span><br><span class="line">    contract_address = <span class="string">&#x27;&lt;合约地址&gt;&#x27;</span></span><br><span class="line">    contract_abi = [] <span class="comment"># 合约abi</span></span><br><span class="line">    caller: LocalAccount = Account.from_key(<span class="string">&#x27;&lt;账户私钥&gt;&#x27;</span>)</span><br><span class="line">    <span class="comment">## 调用view方法</span></span><br><span class="line">    result = call_contract_view_func(w3=w, address=contract_address, abi=contract_abi, func_name=<span class="string">&#x27;getAge&#x27;</span>,</span><br><span class="line">                                     user=<span class="string">&#x27;0xC3845C061D0e0929744B01CbB6c23c31402B3E3a&#x27;</span>)</span><br><span class="line">    <span class="built_in">print</span>(result)</span><br><span class="line">		<span class="comment">## 调用非View方法</span></span><br><span class="line">    tx_hash = call_contract_sign_func(w3=w, account=caller, address=contract_address, abi=contract_abi,</span><br><span class="line">                                      func_name=<span class="string">&#x27;setAge&#x27;</span>,</span><br><span class="line">                                      age=<span class="number">8877</span>)</span><br><span class="line">    <span class="built_in">print</span>(tx_hash.<span class="built_in">hex</span>())</span><br><span class="line">    <span class="comment">## 等待并返回调用结果</span></span><br><span class="line">    resp = w.eth.wait_for_transaction_receipt(tx_hash)</span><br><span class="line">    <span class="built_in">print</span>(resp)</span><br></pre></td></tr></table></figure>

<p>上面的代码中可以看出调用合约非view方法实际上就是一次转账交易，在交易中把对合约方法的调用信息写入交易数据的<code>data</code>字段中，剩下的就是evm需要考虑的事情了。</p>
<h3 id="过滤合约日志"><a href="#过滤合约日志" class="headerlink" title="过滤合约日志"></a>过滤合约日志</h3><p>日志的过滤是合约使用的另一个重要方面，为了便于日志演示，对上面使用的合约进行简单的改造，加上event：</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// SPDX-License-Identifier: GPL-3.0</span></span><br><span class="line">pragma solidity ^<span class="number">0.8</span><span class="number">.9</span>;</span><br><span class="line"></span><br><span class="line">contract <span class="title class_">Demo</span> &#123;</span><br><span class="line">  	<span class="comment">// 日志</span></span><br><span class="line">    event <span class="title class_">SetAge</span>(address user, uint age);</span><br><span class="line">    <span class="title function_">mapping</span>(<span class="function"><span class="params">address</span> =&gt;</span> uint) private _ages;</span><br><span class="line">    <span class="keyword">function</span> <span class="title function_">setAge</span>(<span class="params">uint age</span>) public &#123;</span><br><span class="line">        _ages[msg.<span class="property">sender</span>] = age;</span><br><span class="line">      <span class="comment">// 发射日志</span></span><br><span class="line">        emit <span class="title class_">SetAge</span>(msg.<span class="property">sender</span>, age);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">function</span> <span class="title function_">getAge</span>(<span class="params">address user</span>) public view returns (uint) &#123;</span><br><span class="line">        <span class="keyword">return</span> _ages[user];</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure>

<p>过滤合约日志分如下几步：</p>
<ol>
<li>加载web3实例</li>
<li>加载合约实例</li>
<li>获取合约事件对象</li>
<li>创建事件过滤器</li>
<li>检索过滤后的全部事件</li>
</ol>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> web3 <span class="keyword">import</span> Web3, HTTPProvider</span><br><span class="line"></span><br><span class="line">contract_address = <span class="string">&#x27;&lt;合约地址&gt;&#x27;</span></span><br><span class="line">contract_abi = [] <span class="comment"># 合约ABI</span></span><br><span class="line"><span class="comment"># 加载Web3实例</span></span><br><span class="line">w3 = Web3(HTTPProvider(endpoint_uri=<span class="string">&#x27;http://127.0.0.1:8545&#x27;</span>))</span><br><span class="line"><span class="comment"># 加载合约实例</span></span><br><span class="line">contract_instance = w3.eth.contract(address=contract_address, abi=contract_abi)</span><br><span class="line"><span class="comment"># 获取event对象</span></span><br><span class="line">my_event = contract_instance.events.SetAge()</span><br><span class="line"><span class="comment"># 创建事件过滤器，</span></span><br><span class="line">my_filter = my_event.create_filter(fromBlock=<span class="number">1</span>, toBlock=<span class="number">28</span>)</span><br><span class="line"><span class="comment"># 检索事件</span></span><br><span class="line"><span class="keyword">for</span> event <span class="keyword">in</span> my_filter.get_all_entries():</span><br><span class="line">    <span class="built_in">print</span>(<span class="built_in">dict</span>(event))</span><br><span class="line">   </span><br></pre></td></tr></table></figure>
<p>返回的event数据如下，包含了日志的基本信息：</p>
<ul>
<li>区块信息：区块高度、哈希</li>
<li>交易哈希、交易哈希索引</li>
<li>日志索引</li>
<li>event名</li>
<li>event 数据信息</li>
</ul>
<figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line"> &#x27;args&#x27;<span class="punctuation">:</span>AttributeDict(<span class="punctuation">&#123;</span>&#x27;user&#x27;<span class="punctuation">:</span> &#x27;<span class="number">0xC3845C061D0e0929744B01CbB6c23c31402B3E3a</span>&#x27;<span class="punctuation">,</span> &#x27;age&#x27;<span class="punctuation">:</span> <span class="number">8877</span><span class="punctuation">&#125;</span>)<span class="punctuation">,</span> </span><br><span class="line"> &#x27;event&#x27;<span class="punctuation">:</span> &#x27;SetAge&#x27;<span class="punctuation">,</span></span><br><span class="line"> &#x27;logIndex&#x27;<span class="punctuation">:</span> <span class="number">0</span><span class="punctuation">,</span></span><br><span class="line"> &#x27;transactionIndex&#x27;<span class="punctuation">:</span> <span class="number">0</span><span class="punctuation">,</span></span><br><span class="line"> &#x27;transactionHash&#x27;<span class="punctuation">:</span> HexBytes(&#x27;<span class="number">0x0975e324106b995512a331f0fa29e8af1ef607e41753c84cacf5a6009dda34e7</span>&#x27;)<span class="punctuation">,</span> </span><br><span class="line"> &#x27;address&#x27;<span class="punctuation">:</span> &#x27;<span class="number">0x9d95b127281335A846E4f36e36D45963e452d3D4</span>&#x27;<span class="punctuation">,</span></span><br><span class="line"> &#x27;blockHash&#x27;<span class="punctuation">:</span> HexBytes(&#x27;<span class="number">0x1b67007d4f40c93c1e881a32299541cffe837b8acc64724ac093ae7d9bc4e832</span>&#x27;)<span class="punctuation">,</span></span><br><span class="line"> &#x27;blockNumber&#x27;<span class="punctuation">:</span> <span class="number">28</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br><span class="line"></span><br></pre></td></tr></table></figure>

<p>上面的代码中并没有体现log的topic信息，这里需要首先说明 <strong>Log</strong> 、<strong>event</strong>、<strong>topic</strong>的关系，以太坊的Log数据结构如下：</p>
<figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="string">``</span><span class="string">`go</span></span><br><span class="line"><span class="string">type Log struct &#123;  </span></span><br><span class="line"><span class="string">   // 日志所属的区块高度  </span></span><br><span class="line"><span class="string">   BlockNumber uint64 `</span>json:<span class="string">&quot;blockNumber&quot;</span><span class="string">`  </span></span><br><span class="line"><span class="string">   // 日志所属的交易哈希  </span></span><br><span class="line"><span class="string">   BlockHash common.Hash `</span>json:<span class="string">&quot;blockHash&quot;</span><span class="string">`  </span></span><br><span class="line"><span class="string">   // 日志所属的区块哈希  </span></span><br><span class="line"><span class="string">   TxHash common.Hash `</span>json:<span class="string">&quot;transactionHash&quot;</span> gencodec:<span class="string">&quot;required&quot;</span><span class="string">`  </span></span><br><span class="line"><span class="string">   // 日志所属的交易序列号  </span></span><br><span class="line"><span class="string">   TxIndex uint `</span>json:<span class="string">&quot;transactionIndex&quot;</span><span class="string">`  </span></span><br><span class="line"><span class="string">  </span></span><br><span class="line"><span class="string">   // 日志在交易中的序列，一个交易可以包含多个日志，这个字段表示日志在交易中的索引  </span></span><br><span class="line"><span class="string">   Index uint `</span>json:<span class="string">&quot;logIndex&quot;</span><span class="string">`  </span></span><br><span class="line"><span class="string">   // 是否被移除，注意如果链进行了分叉，那么本字段会变成true，如果为true，那么不应该识别为正确的log  </span></span><br><span class="line"><span class="string">   Removed bool `</span>json:<span class="string">&quot;removed&quot;</span><span class="string">`  </span></span><br><span class="line"><span class="string">  </span></span><br><span class="line"><span class="string">   // 发送出日志的智能合约地址，注意，只有合约账户才能发送event  </span></span><br><span class="line"><span class="string">   Address common.Address `</span>json:<span class="string">&quot;address&quot;</span> gencodec:<span class="string">&quot;required&quot;</span><span class="string">`  </span></span><br><span class="line"><span class="string">   // 日志主题列表，这个字段是为了方便查询时进行过滤  </span></span><br><span class="line"><span class="string">   Topics []common.Hash `</span>json:<span class="string">&quot;topics&quot;</span> gencodec:<span class="string">&quot;required&quot;</span><span class="string">`  </span></span><br><span class="line"><span class="string">   // event携带的数据字节数组  </span></span><br><span class="line"><span class="string">   Data []byte `</span>json:<span class="string">&quot;data&quot;</span> gencodec:<span class="string">&quot;required&quot;</span><span class="string">`  </span></span><br><span class="line"><span class="string">&#125;</span></span><br><span class="line"><span class="string">&#125;</span></span><br><span class="line"><span class="string">`</span><span class="string">``</span></span><br></pre></td></tr></table></figure>

<p>Log 是以太坊中的一个事件记录，是一个 key-value 对的集合，其中 key 是字符串类型的 topics，value 是一个任意长度的字节数组 data。Log 可以被合约 emit，也可以由以太坊系统自动生成，例如在转账时生成的 Transfer Log。每笔交易都有可能会产生 Log。</p>
<p>Event 是合约中的一类特殊函数，它允许合约在执行过程中，向外部环境广播某些内容（包括 Log 和数据）。Event 可以定义多个参数，这些参数可以是基本类型（如 uint256、string 等），也可以是自定义类型。Event 可以被触发多次，并且每次触发都会生成一个独立的 Log。</p>
<p>Topic 是 Log 中的一个结构。在 Emit Event 的时候，每个 Event 参数可能会被标记为 indexed 或不 indexed，indexed 的参数会被记录在 Event 的 Topic 中，不 indexed 的参数则会被记录在 Log 的 Data 中。具体来说，Event 的 keccak 哈希会成为 Topic 的第一个元素，Events 中 indexed 的参数会按照顺序在后面添加。</p>
<p>上面过滤日志的代码中，只是通过合约地址和区块范围进行过滤，如果要过滤event和或者过滤方法参数那么需要在构造过滤器时添加topics字段，</p>
<p>每个log都有一个topic集合，每个event至少有一个topic，即event方法的签名</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">topic=w3.keccak(text=<span class="string">&quot;SetAge(address,uint256)&quot;</span>).<span class="built_in">hex</span>()</span><br></pre></td></tr></table></figure>

<p>如果event的参数被<code>indexed</code>修饰，那么也可以生成topic</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">topic=w3.keccak(text=<span class="string">&quot;user(address)&quot;</span>).<span class="built_in">hex</span>()</span><br></pre></td></tr></table></figure>

<p>在构建过滤器时添加<code>topics</code></p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">my_filter = my_event.create_filter(fromBlock=<span class="number">1</span>, toBlock=<span class="number">28</span>, topics=[w3.keccak(text=<span class="string">&quot;SetAge(address,uint256)&quot;</span>).<span class="built_in">hex</span>()])</span><br></pre></td></tr></table></figure>

<p><code>topic</code>相当于给日志打标签，通过标签可以提高过滤效率，但每次要计算哈希还是挺麻烦的，如果logs数量本身不大，可以直接全量过滤后再通过event name。</p>
<p>Web3.py 还提供了很多其他有用的工具，比如用于解析和生成合约 ABI 编码的方法。但合约的部署、测试一般由hardhat、truffle这类框架完成，使用的是<code>js</code>或者<code>ts</code>,这方面python的存在感比较低。</p>
<p>参考：</p>
<ol>
<li><a target="_blank" rel="noopener" href="https://web3py.readthedocs.io/en/stable/index.html">web3.py官网文档</a></li>
</ol>


<div class="article-footer reveal fs14"><section id="license"><div class="header"><span>许可协议</span></div><div class="body"><p>本文采用 <a target="_blank" rel="noopener" href="https://creativecommons.org/licenses/by-nc-sa/4.0/">署名-非商业性使用-相同方式共享 4.0 国际</a> 许可协议，转载请注明出处。</p>
</div></section></div>

</article>

<div class="related-wrap reveal" id="read-next"><section class="body"><div class="item" id="prev"></div><div class="item" id="next"><div class="note">较早文章</div><a href="/initmatrix/initmatrix/2023/04/20/hello-world/">Hello World</a></div></section></div>








      
<footer class="page-footer reveal fs12"><hr><div class="text"><p>本站由 <a href="/">@anonymity</a> 使用 <a target="_blank" rel="noopener" href="https://github.com/xaoxuu/hexo-theme-stellar">Stellar</a> 主题创建。<br>本博客所有文章除特别声明外，均采用 <a target="_blank" rel="noopener" href="https://creativecommons.org/licenses/by-nc-sa/4.0/">CC BY-NC-SA 4.0</a> 许可协议，转载请注明出处。</p>
</div></footer>

      <div class='float-panel mobile-only blur' style='display:none'>
  <button type='button' class='sidebar-toggle mobile' onclick='sidebar.toggle()'>
    <svg class="icon" style="width: 1em; height: 1em;vertical-align: middle;fill: currentColor;overflow: hidden;" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="15301"><path d="M566.407 808.3c26.9-0.1 49.3-20.8 51.6-47.6-1.9-27.7-23.9-49.7-51.6-51.6h-412.6c-28.2-1.4-52.6 19.5-55.5 47.6 2.3 26.8 24.6 47.5 51.6 47.6h416.5v4z m309.3-249.9c26.9-0.1 49.3-20.8 51.6-47.6-2.2-26.8-24.6-47.5-51.6-47.6h-721.9c-27.7-2.8-52.5 17.4-55.3 45.1-0.1 0.8-0.1 1.7-0.2 2.5 0.9 27.2 23.6 48.5 50.7 47.6H875.707z m-103.1-245.9c26.9-0.1 49.3-20.8 51.6-47.6-0.4-28.3-23.2-51.1-51.5-51.6h-618.9c-29.5-1.1-54.3 21.9-55.5 51.4v0.2c1.4 27.8 25.2 49.2 53 47.8 0.8 0 1.7-0.1 2.5-0.2h618.8z" p-id="15302"></path><path d="M566.407 808.3c26.9-0.1 49.3-20.8 51.6-47.6-1.9-27.7-23.9-49.7-51.6-51.6h-412.6c-28.2-1.4-52.6 19.5-55.5 47.6 1.9 27.7 23.9 49.7 51.6 51.6h416.5z m309.3-249.9c26.9-0.1 49.3-20.8 51.6-47.6-2.2-26.8-24.6-47.5-51.6-47.6h-721.9c-27.7-2.8-52.5 17.4-55.3 45.1-0.1 0.8-0.1 1.7-0.2 2.5 0.9 27.2 23.6 48.5 50.7 47.6H875.707z m-103.1-245.9c26.9-0.1 49.3-20.8 51.6-47.6-0.4-28.3-23.2-51.1-51.5-51.6h-618.9c-29.5-1.1-54.3 21.9-55.5 51.4v0.2c1.4 27.8 25.2 49.2 53 47.8 0.8 0 1.7-0.1 2.5-0.2h618.8z" p-id="15303"></path></svg>
  </button>
</div>

    </div>
  </div>
  <div class='scripts'>
    <script type="text/javascript">
  const stellar = {
    // 懒加载 css https://github.com/filamentgroup/loadCSS
    loadCSS: (href, before, media, attributes) => {
      var doc = window.document;
      var ss = doc.createElement("link");
      var ref;
      if (before) {
        ref = before;
      } else {
        var refs = (doc.body || doc.getElementsByTagName("head")[0]).childNodes;
        ref = refs[refs.length - 1];
      }
      var sheets = doc.styleSheets;
      if (attributes) {
        for (var attributeName in attributes) {
          if (attributes.hasOwnProperty(attributeName)) {
            ss.setAttribute(attributeName, attributes[attributeName]);
          }
        }
      }
      ss.rel = "stylesheet";
      ss.href = href;
      ss.media = "only x";
      function ready(cb) {
        if (doc.body) {
          return cb();
        }
        setTimeout(function () {
          ready(cb);
        });
      }
      ready(function () {
        ref.parentNode.insertBefore(ss, before ? ref : ref.nextSibling);
      });
      var onloadcssdefined = function (cb) {
        var resolvedHref = ss.href;
        var i = sheets.length;
        while (i--) {
          if (sheets[i].href === resolvedHref) {
            return cb();
          }
        }
        setTimeout(function () {
          onloadcssdefined(cb);
        });
      };
      function loadCB() {
        if (ss.addEventListener) {
          ss.removeEventListener("load", loadCB);
        }
        ss.media = media || "all";
      }
      if (ss.addEventListener) {
        ss.addEventListener("load", loadCB);
      }
      ss.onloadcssdefined = onloadcssdefined;
      onloadcssdefined(loadCB);
      return ss;
    },

    // 从 butterfly 和 volantis 获得灵感
    loadScript: (src, opt) => new Promise((resolve, reject) => {
      var script = document.createElement('script');
      if (src.startsWith('/')){
        src = stellar.config.root + src.substring(1);
      }
      script.src = src;
      if (opt) {
        for (let key of Object.keys(opt)) {
          script[key] = opt[key]
        }
      } else {
        // 默认异步，如果需要同步，第二个参数传入 {} 即可
        script.async = true
      }
      script.onerror = reject
      script.onload = script.onreadystatechange = function() {
        const loadState = this.readyState
        if (loadState && loadState !== 'loaded' && loadState !== 'complete') return
        script.onload = script.onreadystatechange = null
        resolve()
      }
      document.head.appendChild(script)
    }),

    // https://github.com/jerryc127/hexo-theme-butterfly
    jQuery: (fn) => {
      if (typeof jQuery === 'undefined') {
        stellar.loadScript(stellar.plugins.jQuery).then(fn)
      } else {
        fn()
      }
    }
  };
  stellar.version = '1.18.5';
  stellar.github = 'https://github.com/xaoxuu/hexo-theme-stellar/tree/1.18.5';
  stellar.config = {
    date_suffix: {
      just: '刚刚',
      min: '分钟前',
      hour: '小时前',
      day: '天前',
      month: '个月前',
    },
    root : '/initmatrix/initmatrix/',
  };

  // required plugins (only load if needs)
  stellar.plugins = {
    jQuery: 'https://gcore.jsdelivr.net/npm/jquery@3.6.2/dist/jquery.min.js'
  };

  if ('local_search') {
    stellar.search = {};
    stellar.search.service = 'local_search';
    if (stellar.search.service == 'local_search') {
      let service_obj = Object.assign({}, {"field":"all","path":"/search.json","content":true,"sort":"-date"});
      stellar.search[stellar.search.service] = service_obj;
    }
  }

  // stellar js
  stellar.plugins.stellar = Object.assign({"sites":"/js/plugins/sites.js","friends":"/js/plugins/friends.js","ghinfo":"/js/plugins/ghinfo.js","timeline":"/js/plugins/timeline.js","linkcard":"/js/plugins/linkcard.js","fcircle":"/js/plugins/fcircle.js","weibo":"/js/plugins/weibo.js"});

  stellar.plugins.marked = Object.assign("https://cdn.bootcdn.net/ajax/libs/marked/4.0.18/marked.min.js");
  // optional plugins
  if ('true' == 'true') {
    stellar.plugins.lazyload = Object.assign({"enable":true,"js":"https://gcore.jsdelivr.net/npm/vanilla-lazyload@17.8.3/dist/lazyload.min.js","transition":"blur"});
  }
  if ('true' == 'true') {
    stellar.plugins.swiper = Object.assign({"enable":true,"css":"https://unpkg.com/swiper@8.4.5/swiper-bundle.min.css","js":"https://unpkg.com/swiper@8.4.5/swiper-bundle.min.js"});
  }
  if ('' == 'true') {
    stellar.plugins.scrollreveal = Object.assign({"enable":null,"js":"https://gcore.jsdelivr.net/npm/scrollreveal@4.0.9/dist/scrollreveal.min.js","distance":"8px","duration":500,"interval":100,"scale":1});
  }
  if ('true' == 'true') {
    stellar.plugins.preload = Object.assign({"enable":true,"service":"flying_pages","instant_page":"https://gcore.jsdelivr.net/gh/volantis-x/cdn-volantis@4.1.2/js/instant_page.js","flying_pages":"https://gcore.jsdelivr.net/gh/gijo-varghese/flying-pages@2.1.2/flying-pages.min.js"});
  }
  if ('true' == 'true') {
    stellar.plugins.fancybox = Object.assign({"enable":true,"js":"https://gcore.jsdelivr.net/npm/@fancyapps/ui@4.0/dist/fancybox.umd.js","css":"https://gcore.jsdelivr.net/npm/@fancyapps/ui@4.0/dist/fancybox.css","selector":".swiper-slide img"});
  }
  if ('false' == 'true') {
    stellar.plugins.heti = Object.assign({"enable":false,"css":"https://unpkg.com/heti@0.9.2/umd/heti.min.css","js":"https://unpkg.com/heti@0.9.2/umd/heti-addon.min.js"});
  }
</script>

<!-- required -->

  
<script src="/initmatrix/initmatrix/js/main.js" async></script>



<!-- optional -->



<!-- inject -->


  </div>
</body>
</html>
